/*
 * Copyright (C) 2017 Haoge
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.lzh.compiler.parceler;

import com.lzh.compiler.parceler.annotation.BundleConverter;
import ohos.aafwk.content.IntentParams;
import ohos.rpc.IRemoteObject;
import ohos.utils.PacMap;
import ohos.utils.PlainArray;
import ohos.utils.Sequenceable;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;

/**
 * <p>此类只应被{@link BundleFactory}使用。用于真正从{@link PacMap}数据源中读取或者存储数据。
 *
 * @author haoge
 */
final class BundleHandle {
    private static BundleHandle handle = new BundleHandle();

    private BundleHandle() {
    }

    static BundleHandle get() {
        return handle;
    }

    /**
     * 将任意类型数据data使用key放入Bundle中。
     *
     * <ul>
     *     <li>如果data的数据类型能直接被放入{@link PacMap}, 则直接放入</li>
     *     <li>如果data的数据类型不能直接被放入{@link PacMap}, 则使用转换器进行数据转换再放入</li>
     * </ul>
     *
     * @param bundle    用于存储数据的容器.
     * @param key       用于存储数据的key值
     * @param data      需要被放置的数据data
     * @param converter 数据转换器实例
     */
    void toBundle(IntentParams bundle, String key, Object data, BundleConverter converter) {
        try {
            toBundleInternal(bundle, key, data);
        } catch (Throwable t) {
            if (converter != null) {
                data = converter.convertToBundle(data);
                toBundle(bundle, key, data, null);
            } else {
                throw t;
            }
        }
    }

    private void toBundleInternal(IntentParams bundle, String key, Object data) {
        Class<?> type = data.getClass();
        if (type.isAssignableFrom(int.class)
                || data.getClass().isAssignableFrom(Integer.class)) {
            bundle.setParam(key, data);
        } else if (type.isAssignableFrom(boolean.class)
                || type.isAssignableFrom(Boolean.class)) {
            bundle.setParam(key, data);
        } else if (type.isAssignableFrom(byte.class)
                || type.isAssignableFrom(Byte.class)) {
            bundle.setParam(key, data);
        } else if (type.isAssignableFrom(char.class)
                || type.isAssignableFrom(Character.class)) {
            bundle.setParam(key, data);
        } else if (type.isAssignableFrom(long.class)
                || type.isAssignableFrom(Long.class)) {
            bundle.setParam(key, data);
        } else if (type.isAssignableFrom(float.class)
                || type.isAssignableFrom(Float.class)) {
            bundle.setParam(key, data);
        } else if (type.isAssignableFrom(double.class)
                || type.isAssignableFrom(Double.class)) {
            bundle.setParam(key, data);
        } else if (type.isAssignableFrom(byte[].class)) {
            bundle.setParam(key, data);
        } else if (type.isAssignableFrom(char[].class)) {
            bundle.setParam(key, data);
        } else if (type.isAssignableFrom(int[].class)) {
            bundle.setParam(key, data);
        } else if (type.isAssignableFrom(long[].class)) {
            bundle.setParam(key, data);
        } else if (type.isAssignableFrom(float[].class)) {
            bundle.setParam(key, data);
        } else if (type.isAssignableFrom(double[].class)) {
            bundle.setParam(key, data);
        } else if (type.isAssignableFrom(boolean[].class)) {
            bundle.setParam(key, data);
        } else if (type.isAssignableFrom(String.class)) {
            bundle.setParam(key, data);
        } else if (type.isAssignableFrom(String[].class)) {
            bundle.setParam(key, data);
        } else if (PacMap.class.isInstance(data)) {
            bundle.setParam(key, data);
        } else if (IRemoteObject.class.isInstance(data)) {
            assert data instanceof PacMap;
            bundle.setParam(key, data);
        } else if (data instanceof CharSequence) {
            bundle.setParam(key, data);
        } else if (data instanceof CharSequence[]) {
            bundle.setParam(key, data);
        } else if (data instanceof Sequenceable) {
            bundle.setParam(key, data);
        } else if (data instanceof Sequenceable[]) {
            bundle.setParam(key, data);
        } else if (data instanceof Serializable
                && !(data instanceof Collection)
                && !data.getClass().isArray()) {
        } else {
            toBundleFromGenericType(bundle, key, data);
        }
    }

    @SuppressWarnings("unchecked")
    private boolean toBundleFromArrayList(IntentParams bundle, String key, ArrayList list) {
        if (list.isEmpty()) {
            bundle.setParam(key, list);
            return true;
        }

        boolean handle = false;
        Object item = list.get(0);
        if (item instanceof Integer) {
            bundle.setParam(key, list);
            handle = true;
        } else if (item instanceof Sequenceable) {
            bundle.setParam(key, list);
            handle = true;
        } else if (item instanceof String) {
            bundle.setParam(key, list);
            handle = true;
        } else if (item instanceof CharSequence) {
            bundle.setParam(key, list);
            handle = true;
        }
        return handle;
    }

    @SuppressWarnings("unchecked")
    private boolean toBundleFromSparseArray(IntentParams bundle, String key, PlainArray list) {
        if (list.size() == 0) {
            bundle.setParam(key, list);
            return true;
        }
        Object item = list.get(list.keyAt(0));
        if (item instanceof Sequenceable) {
            bundle.setParam(key, list);
            return true;
        }
        return false;
    }

    @SuppressWarnings({"unchecked", "ConstantConditions"})
    private void toBundleFromGenericType(IntentParams bundle, String key, Object data) {
        if ((data instanceof ArrayList) && toBundleFromArrayList(bundle, key, (ArrayList) data)) {
            return;
        }
        if (data instanceof PlainArray && toBundleFromSparseArray(bundle, key, (PlainArray) data)) {
            return;
        }
        throw new RuntimeException("Could not put data to bundle");
    }

    /**
     * <p>将指定数据data转换为指定类型type数据并返回。
     *
     * <ul>
     *     <li>当data能直接与数据类型type匹配时，直接返回原始数据data</li>
     *     <li>当data不能与type匹配时，使用数据转换器对原始数据data进行转换后再返回。</li>
     * </ul>
     *
     * @param data      指定的数据data
     * @param type      指定数据类型type
     * @param converter 数据转换器
     * @return 转换后的指定type类型的数据。
     */
    Object cast(Object data, Type type, BundleConverter converter) {
        return castInternal(data, type, converter);
    }

    private Object castInternal(Object data, Type type, BundleConverter converter) {
        Class raw = getRawClass(type);
        if (raw.isInstance(data)) return data;

        if (!(data instanceof String)) {
            if (converter == null) {
                throw new RuntimeException("Should only handle with String." +
                        " Please check if you had set a converter to used?");
            } else {
                data = converter.convertToBundle(data);
            }
        }

        String value = (String) data;
        if (TextUtils.isEmpty(value)) {
            return returnsValue(null, raw);
        }

        switch (raw.getCanonicalName()) {
            case "byte":
            case "java.lang.Byte":
                return Byte.valueOf(value);
            case "short":
            case "java.lang.Short":
                return Short.valueOf(value);
            case "int":
            case "java.lang.Integer":
                return Integer.valueOf(value);
            case "long":
            case "java.lang.Long":
                return Long.valueOf(value);
            case "float":
            case "java.lang.Float":
                return Float.valueOf(value);
            case "double":
            case "java.lang.Double":
                return Double.valueOf(value);
            case "char":
            case "java.lang.Character":
                return value.charAt(0);
            case "boolean":
            case "java.lang.Boolean":
                return Boolean.valueOf(value);
            case "java.lang.StringBuffer":
                return new StringBuffer(value);
            case "java.lang.StringBuilder":
                return new StringBuilder(value);
            default:
                if (converter != null) {
                    return converter.convertToEntity(value, type);
                } else {
                    return returnsValue(null, raw);
                }
        }
    }

    // 兼容基本数据类型返回，对返回数据进行二次处理。避免对基本数据类型返回null导致crash
    private Object returnsValue(Object value, Class type) {
        if (value != null) return value;
        switch (type.getCanonicalName()) {
            case "byte":
            case "short":
            case "int":
                return 0;
            case "long":
                return 0L;
            case "float":
                return 0f;
            case "double":
                return 0d;
            case "char":
                return '0';
            case "boolean":
                return false;
            default:
                return null;
        }
    }

    private Class getRawClass(Type type) {
        if (type instanceof Class) {
            return (Class) type;
        } else if (type instanceof ParameterizedType) {
            return getRawClass(((ParameterizedType) type).getRawType());
        } else {
            throw new RuntimeException("Only support of Class and ParameterizedType");
        }
    }
}
